package com.qfoud.edu.framwork.listener;
import com.qfoud.edu.framwork.annotation.*;
import com.qfoud.edu.framwork.container.InterceptorContainer;
import com.qfoud.edu.framwork.container.MappingContainer;
import com.qfoud.edu.framwork.exception.HandlerExceptionResolve;
import com.qfoud.edu.framwork.exception.MvcFramworkException;
import com.qfoud.edu.framwork.interceptor.base.AbstractInterceptor;
import com.qfoud.edu.framwork.pojo.BeanDefinition;
import com.qfoud.edu.framwork.pojo.BeanMethodDefinition;
import com.qfoud.edu.framwork.pojo.BeanParameterDefinition;
import com.qfoud.edu.framwork.utils.StringUtils;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;

/**
 * @author xiaobobo
 * @title: CoreApplicationListener
 * @projectName cd-java-2303-project-parent
 * @description: 这里就是监听应用程序的启动
 *
 * 在这个类中我们向干一件事情?
 * 就是找到 拥有 @Controller注解的类 并且将这些类 解析出来
 * 以 @RequestMapping的地址为key  以method 类实例 等封装的类对象 为值
 * 放到一个ConcurrentHashMap中去
 *
 * 为了查询拥有 @Controller注解的类 的效率更高 我们再定义一个注解 @CompnentScan
 * 以这个注解为切入点来找 类的class对象
 *
 * @date 2023/5/8  10:59
 */
@WebListener
public class CoreApplicationListener implements ServletContextListener {

    //存储绝对路径下的所有的文件的容器
    private static final List<File> allFiles=new CopyOnWriteArrayList<>();

    //这里面是存放interceptor的所有文件
    private static final List<File> allInterceptorFiles=new CopyOnWriteArrayList<>();


    public void contextInitialized(ServletContextEvent sce) {
        //这里的初始化操作是可以放到线程中去执行
        Executors.newSingleThreadExecutor().execute(()->{
            initComponentScan(sce);
            //这里专门用来初始化我们的Interceptor
            try {
                initInterceptor(sce);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });


    }

    /**
     * 初始化ComponentSan这个注解
     * @param sce
     */
    private void initComponentScan(ServletContextEvent sce) {
        String scanPath=getComponentScanPath(sce);
        //程序执行到这里 说明：配置了路径 一切都是OK的
        //获取扫描路径的绝对路径
        String absolutePath=getAbsolutePath(scanPath,sce);
        //获取这个绝对路径下所有的文件
        listAllFiles(absolutePath);
        //分离出这些文件中的class文件
        List<File> fileList= listClassFiles("controller");
        //找到这些class文件的class对象回来
        List<Class> classFileList=listClass(fileList,sce);
        findOnConditionClass(classFileList);
    }

    /**
     * 这里专门用来初始化interceptor
     * @param sce
     */
    private void initInterceptor(ServletContextEvent sce) throws InstantiationException, IllegalAccessException {
        //这里就构成了所有类的基路径
        String path=sce.getServletContext().getRealPath("/")+"WEB-INF\\classes\\";
        //遍历所有的文件
        listAllInterceptorFiles(path);
        //找到所有的class文件
        List<File> fileList= listClassFiles("interceptor");
        //找到这些class文件的class对象回来
        List<Class> classFileList=listClass(fileList,sce);
        //找到这些class中是否有 @interceptor注解
        findOnConditionClassHavingInterceptor(classFileList);
    }

    /**
     * 找到这些class中是否有Interceptor注解
     * @param classFileList
     */
    private void findOnConditionClassHavingInterceptor(List<Class> classFileList) throws IllegalAccessException, InstantiationException {
        //遍历我们的这个集合
        for (int i = 0; i <classFileList.size() ; i++) {
            Class clazz = classFileList.get(i);
            //判断这个类是否拥有@Interceptor注解
            Interceptor interceptor= (Interceptor) clazz.getAnnotation(Interceptor.class);
            if(null==interceptor){
                //说明这个类没有Interceptor注解
                continue;
            }
            //然后再获取这个类的Order注解
            Order order = (Order) clazz.getAnnotation(Order.class);
            if(null!=order){
                //说明配置了这个注解
                //那么说明可以取出这个顺序
                int order1 = order.order();
                //接下来需要按照顺序将这个拦截器存储到容器中
                AbstractInterceptor interceptorInstance = (AbstractInterceptor) clazz.newInstance();
                //接下来存储到容器中
                InterceptorContainer.getInterceptorMap().put(order1,interceptorInstance);
                System.out.println("------------拦截器初始化完成-----------------");
            }else{
                throw new MvcFramworkException("拦截器未配置@Order注解");
            }

        }


    }

    /**
     * 找到复合条件的class对象并进行扫描@Controller注解
     * @param classFileList
     */
    private void findOnConditionClass(List<Class> classFileList) {
        //遍历所有的class对象
        for (Class clazz:classFileList) {
            //判断这个class对象是否有@Controller注解
            Annotation annotation = clazz.getAnnotation(Controller.class);
            if(null==annotation){
                //说明这个类没有Controller注解
                //直接运行到下一次循环
                //说明是一个普通的类
                continue;
            }
            //这里需要去描述类以及类方法 以及参数
            try {
                handlerType(clazz);
                System.out.println("容器:"+MappingContainer.getMappingContainer());
            } catch (Exception e) {
                e.printStackTrace();
                throw new MvcFramworkException("解析类出错....");
            }
        }
    }

    /**
     * 这里用来描述类
     * @param clazz
     */
    private void handlerType(Class clazz) throws IllegalAccessException, InstantiationException {
        //执行到这里 说明有这个注解
        //获取这个类上的@RequestMapping注解
        RequestMapping requestMapping  = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
        //这里进行方法的描述
        //获取这个类中所有的方法
        Method[] methods = clazz.getMethods();
        //接下来遍历方法
        for (Method method:methods) {
            BeanDefinition beanDefinition = parseType(clazz, requestMapping);
            //判断这个方法是否有@RequestMapping注解
            RequestMapping requestMappingMethod = method.getAnnotation(RequestMapping.class);
            if(null==requestMappingMethod){
                //说明这个方法没有这个注解
                continue;
            }
            //执行到这里说明这个方法有RequestMapping这个注解
            BeanMethodDefinition beanMethodDefinition = new BeanMethodDefinition();
            //这里还需要判断是否有ResponseBody这个注解
            ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
            if(null!=responseBody){
                //说明有这个注解
                //将这个有的状态放到描述类中
                beanMethodDefinition.setResponseBodyOrNot(true);
            }

            //设置方法
            beanMethodDefinition.setMethod(method);
            //设置方法的名字
            beanMethodDefinition.setMethodName(method.getName());
            //设置这个方法的RequestMapping的地址
            beanMethodDefinition.setRequestMappingPath(requestMappingMethod.value());
            //接下来：解析方法的参数
            Parameter[] parameters = method.getParameters();

            //申明一个装参数描述的容器
            List<BeanParameterDefinition> parameterDefinitions=new ArrayList<>();
            //遍历方法的每一个参数
            for (Parameter parameter:parameters) {
                BeanParameterDefinition beanParameterDefinition = new BeanParameterDefinition();
                //这里需要解析RequestBody这个注解
                RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
                if(null!=requestBody){
                    //说明有这个注解 前端传递的参数需要转换成Java对象
                    beanParameterDefinition.setRequestBodyOrNot(true);
                }
                //设置参数的名字
                beanParameterDefinition.setParameterName(parameter.getName());
                //获取参数的类型
                beanParameterDefinition.setParameterType(parameter.getType().getTypeName());
                parameterDefinitions.add(beanParameterDefinition);
            }
            beanMethodDefinition.setBeanParameterDefinitions(parameterDefinitions);
            //最终的目标 将方法的描述添加到类的描述中去
            beanDefinition.setBeanMethodDefinition(beanMethodDefinition);
            //最终目标是为了存映射关系
            String key=beanDefinition.getRequestMappingPath()
                    +beanMethodDefinition.getRequestMappingPath().trim();
            MappingContainer.getMappingContainer().put(key,beanDefinition);
        }
        System.out.println("-------------@ComponentScan注解初始化完成---------------");
    }

    /**
     * 解析咋们的类
     * @param clazz
     * @param requestMapping
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private BeanDefinition parseType(Class clazz, RequestMapping requestMapping) throws InstantiationException, IllegalAccessException {
        BeanDefinition beanDefinition = new BeanDefinition();
        //下面这个变量用来存放我们的类上面的RequestMapping的地址
        String typeReqPath="";
        if(null!=requestMapping){
            //说明类上面是有这个@RequestMapping注解的
            typeReqPath=requestMapping.value();
            beanDefinition.setRequestMappingOrNot(true);
        }else{
            //说明类上面没有这个注解
            typeReqPath="";
        }
        //找到类的实例
        Object instance = clazz.newInstance();
        beanDefinition.setInstance(instance);
        //这个就是类的名字
        beanDefinition.setTypeName(clazz.getSimpleName());
        //设置这个请求的路径
        beanDefinition.setRequestMappingPath(typeReqPath);
        return beanDefinition;
    }

    /**
     * 找到class文件的class对象然后放到集合中
     * @param fileList
     * @return
     */
    private static List<Class> listClass(List<File> fileList,ServletContextEvent sce) {
        //首先申明容器
        List<Class> classes=new ArrayList<>();
        //首先准备我们的部署的根路径
        String realPath = sce.getServletContext().getRealPath("/")+"WEB-INF\\classes\\";
        //中间经历无数个步骤
        for (File file:fileList) {
            //获取当前文件的绝对路径
            String absolutePath = file.getAbsolutePath();
            // G:/部署根路径/   com/efoud/aunt/commons/controller/UserController    .class
            String classPath = absolutePath.replace(realPath, "");
            String substring = classPath.substring(0, classPath.lastIndexOf(".class"));
            String classPath1 = substring.replace("\\", ".");
            try {
                Class<?> aClass = Class.forName(classPath1);
                classes.add(aClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return classes;
    }


    /**
     * 分离出这个文件中的所有的Class文件
     * @return
     */
    private List<File> listClassFiles(String tag) {
        List<File> fileList=new ArrayList<>();
        //TODO ：这里是可以使用到 JDK8中的stream来处理的
        //这里跟遍历的文件有关系
        List<File> scanFile=null;
        if("controller".equals(tag)){
            scanFile=allFiles;
        }else{
            scanFile=allInterceptorFiles;
        }
        for (File file:scanFile) {
            String absolutePath = file.getAbsolutePath();
            if(absolutePath.endsWith(".class")){
                fileList.add(file);
            }
        }
        return fileList;
    }

    /**
     * 通过绝对路径找所有的文件
     * 找到之后存储到 allFiles中
     * @param absolutePath
     */
    private void listAllFiles(String absolutePath) {
        File file1 = new File(absolutePath);
        File[] files = file1.listFiles();
        if(null!=files&&files.length>0){
            for (File f:files) {
                if(f.isFile()){
                    allFiles.add(f);
                }else if(f.isDirectory()){
                    //说明是一个文件夹
                    listAllFiles(f.getAbsolutePath());
                }
            }
        }
    }


    /**
     * 遍历所有的文件
     * @param absolutePath
     */
    private void listAllInterceptorFiles(String absolutePath) {
        File file1 = new File(absolutePath);
        File[] files = file1.listFiles();
        if(null!=files&&files.length>0){
            for (File f:files) {
                if(f.isFile()){
                    allInterceptorFiles.add(f);
                }else if(f.isDirectory()){
                    //说明是一个文件夹
                    listAllInterceptorFiles(f.getAbsolutePath());
                }
            }
        }

    }

    /**
     * 通过设置的扫描路径去获取这个扫描路径的绝对路径
     * 这个路径记住：部署的路径(项目部署路径的绝对路径)
     * @param scanPath
     * @param sce
     * @return
     */
    private String getAbsolutePath(String scanPath, ServletContextEvent sce) {
        //获取项目部署的根目录
        String realPath = sce.getServletContext().getRealPath("/")+"WEB-INF\\classes\\";
        //将扫描路径中的. 换成 \\
        scanPath=scanPath.replace(".","\\");
        realPath=realPath+scanPath;
        System.out.println("realPath="+realPath);
        return realPath;
    }

    /**
     * 这个方法用来获取控制器的扫描路径
     * @param sce
     */
    private String getComponentScanPath(ServletContextEvent sce) {
        //找配置文件的class对象
        Class<?> configClass = getConfigClass(sce);
        //执行到这里说明不为空
        //接下来进行注解的获取
        ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
        //接下来判断是否有这个注解
        if(null==componentScan){
           //说明这个配置文件没有这个注解
           throw new MvcFramworkException("未配置@ComponentScan这个注解");
        }
        //程序执行到这里 说明：配置了这个注解
        //获取这个注解的值
        String scanPath = componentScan.scan();
        //判断这个值是否为空
        if(StringUtils.isEmpty(scanPath)){
            throw new MvcFramworkException("未配置@ComponentScan的扫描路径");
        }
        return scanPath;
    }

    /**
     * 找配置文件的class对象
     * @param sce
     * @return
     */
    private Class<?> getConfigClass(ServletContextEvent sce) {
        //第一步：找到配置文件
        String config = sce.getServletContext().getInitParameter("config");
        if(StringUtils.isEmpty(config)){
           throw new MvcFramworkException("web.xml中未配置config节点");
        }
        //程序执行到这里说明配置了节点
        //第二步：反射获取这个类的class对象
        Class<?> configClass=null;
        try {
            configClass = Class.forName(config);
        } catch (ClassNotFoundException e) {
            throw new MvcFramworkException("config中的类路径有误");
        }
        return configClass;
    }
}
